/*
 * Copyright 2020 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cysoft.domain.solver;

import com.cysoft.App;
import com.cysoft.domain.*;
import org.optaplanner.core.api.domain.variable.VariableListener;
import org.optaplanner.core.api.score.director.ScoreDirector;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Objects;


/**
 * @author caiye
 */ //每次,有Task对象的previousStep的property发生更改后,这个监听类就会监听到,然后执行相应操作
public class StartTimeUpdatingVariableListener implements VariableListener<TaskSolution, Task> {

    @Override
    public void beforeEntityAdded(ScoreDirector<TaskSolution> scoreDirector, Task task) {
        // Do nothing
    }

    @Override
    public void afterEntityAdded(ScoreDirector<TaskSolution> scoreDirector, Task task) {
        updateStartTime(scoreDirector, task);
    }

    @Override
    public void beforeVariableChanged(ScoreDirector<TaskSolution> scoreDirector, Task task) {
        // Do nothing
    }

    @Override
    public void afterVariableChanged(ScoreDirector<TaskSolution> scoreDirector, Task task) {
        updateStartTime(scoreDirector, task);
    }

    @Override
    public void beforeEntityRemoved(ScoreDirector<TaskSolution> scoreDirector, Task task) {
        // Do nothing
    }

    @Override
    public void afterEntityRemoved(ScoreDirector<TaskSolution> scoreDirector, Task task) {
        // Do nothing
    }

    protected void updateStartTime(ScoreDirector<TaskSolution> scoreDirector, Task sourceTask) {
        //首先取得该Task对象的前一个实体或锚
        Step previous = sourceTask.getPreviousStep();
        //保存一下当前Task对象
        Task shadowTask = sourceTask;

        //取得前面的截止时间
        LocalDateTime previousEndTime = (previous == null ? null : previous.getEndDateTime());
//        if(previous != null && previous instanceof Machine) {
//            machineMap.put(shadowTask.getId(),(Machine)previous);
//            machineList.add((Machine)previous);
//        }

        //通过设备上的任务链和父子节点关系求得bomTask的值
        //开始时间取，前面任务的结束时间，如果后期想要给任务拓展加上前置时间后置时间的话，可以更改一下这个方法
        //刚开始的时候，previousEndTime是null，所以startTime也是null
        LocalDateTime startTime = calculateStartTime2(shadowTask, previousEndTime);

//        if(previous != null && previous instanceof Machine) {
//            System.out.print("机台："+((Machine) previous).getName());
//            while (shadowTask != null) {
//                System.out.print("->任务链：" + shadowTask.getTaskName());
//                shadowTask = shadowTask.getNextTask();
//            }
//            System.out.println("\n");
//        }
        //当用来遍历task不是null并且shadowTask的startTime和前面计算的startTime不相等的时候，循环
        //用来遍历的shadowTask一直向后取，一直取到后面为null的时候，也就是previousTaskOrEmployee属性变更的task后面的所有task的startTime全部变更完毕的时候循环停止
        //刚开始的时候，previousEndTime和startTime都是null，所以相等，这种情况就不用更新了，然后后面成链的时候这里才比较有用
        //还有一种就是，两个任务的持续时间一样，这样的话两个任务交换以后后面的任务的时间就不用更新了
        //这个while是进行，这条链上改变节点之后的级联变更，还需要，子节点改变时，其余链上的父节点也会变更，而且，这个变更需要每一次改变都要进行父节点相应的改变，所以需要嵌套在这个while里面
        while (shadowTask != null && !equalTime(shadowTask.getBeginDateTime(), startTime)) {
            scoreDirector.beforeVariableChanged(shadowTask, "beginDateTime");
            shadowTask.setBeginDateTime(startTime);

            if(shadowTask.getMold() != null) {
                shadowTask.setMold(App.moldService.assignMold(shadowTask.getMold().getTaskId(), shadowTask.getWheelCode(), shadowTask.getBeginDateTime(), shadowTask.getEndDateTime()));
            } else {
                shadowTask.setMold(App.moldService.assignMold(shadowTask.getWheelCode(), shadowTask.getBeginDateTime(), shadowTask.getEndDateTime()));
            }

            scoreDirector.afterVariableChanged(shadowTask, "beginDateTime");

            //前面的截止时间设置为当前任务的截止时间
            previousEndTime = shadowTask.getEndDateTime();
            //shadowTask继续向后取task
            shadowTask = shadowTask.getNextTask();
            //开始时间取，前面的截止时间
            startTime = calculateStartTime2(shadowTask, previousEndTime);
//            System.out.println(System.currentTimeMillis() +  " calculateStartTime 3");
            if(shadowTask != null) {
                System.out.println("=========机台："+((Machine) previous).getName()+" , 分配多任务->"+shadowTask.getTaskName());
            }
        }
    }

    private LocalDateTime calculateStartTime(Task task, LocalDateTime previousEndTime) {
        //如果传进来的task是null，也就是没有到任务链的末端；
        System.out.println(System.currentTimeMillis() +  " calculateStartTime begin");
        if(task == null) {
            System.out.println("calculateStartTime task is null");
            return null;
        }
        if(task == null || task.getWheel() == null) {
            System.out.println("calculateStartTime task is null and wheel is null");
            return null;
        }

        task.setTaskCoastTime((int)(task.getCount() * task.getWheel().getCostTime()));

        LocalDateTime startTime = null;
        //或者previousEndTime是null，也是不是在任务链的开头
        if (previousEndTime == null) {
            System.out.println("calculateStartTime previousEndTime is null,task:"+task.getTaskName());
            return null;
        } else {
            startTime = task.getPaymentTime().minusMinutes(task.getTaskCoastTime());
        }
        System.out.println(System.currentTimeMillis() +  " calculateStartTime end");
        return startTime;
    }

    private boolean equalTime(LocalDateTime a,LocalDateTime b) {
        if(a==null || b==null) {
            return false;
        }
        if(Duration.between(a,b).getSeconds() != 0) {
            return  false;
        }
        return true;
    }
    //开始时间取，前面的截止时间和当前任务的准备时间的最大值

    /**
     * 计算任务的截止时间
     * 前一个任务的结束时间+虚拟任务时间（任务之间切换需要的时间，同轮型同模具很小，不同模具6小时？）
     * @param task
     * @param previousEndTime
     * @return
     */
    private LocalDateTime calculateStartTime2(Task task, LocalDateTime previousEndTime) {
        //如果传进来的task是null，也就是没有到任务链的末端；
        if(task == null) {
//            System.out.println("calculateStartTime task is null，此任务链结束");
            return null;
        }
        if(previousEndTime == null) {
//            System.out.println("calculateStartTime previousEndTime is null，按照原时间，不需要规划");
            return null;
        }

        LocalDateTime beginDateTime = task.getBeginDateTime();
        if(beginDateTime == null) {
            beginDateTime = Constant.BEGIN_DATE;
        }

        LocalDateTime startTime = maxTime(previousEndTime,beginDateTime);
//        System.out.println(task.getTaskName() + ": calculateStartTime 开始规划:"+task.getMachine().getName()+" ，开始时间："+startTime.toString());

//        System.out.println(System.currentTimeMillis() +  " calculateStartTime2 end");
        return startTime;
    }

    LocalDateTime minTime(LocalDateTime a,LocalDateTime b){
        if(a==null || b==null){
            return null;
        }
        if(a.compareTo(b)<0) {
            return a;
        } else {
            return b;
        }
    }
    LocalDateTime maxTime(LocalDateTime a,LocalDateTime b){
        if(a==null){
            return b;
        }
        if(a.compareTo(b)>0) {
            return a;
        } else {
            return b;
        }
    }
}
